SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
11 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
5.2 KB · 88 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import { notFound } from 'next/navigation';3import { requireUser } from '@/lib/auth/session';4import { getCollectionDetail } from '@/lib/account/queries';5import { getDisplay } from '@/lib/account/display';6import { confidenceLabel, fmtDate } from '@/lib/format';7import { PrintButton } from './print-button';89export const metadata: Metadata = { title: 'Insurance schedule', robots: { index: false } };1011/** Printable schedule of items with values, sources and confidence — a document, not an appraisal. */12export default async function InsurancePage({ params }: { params: Promise<{ id: string }> }) {13  const { id } = await params;14  const u = await requireUser(`/collections/${id}/insurance`);15  const detail = await getCollectionDetail(u.id, id);16  if (!detail) notFound();17  const d = await getDisplay();18  const { collection: c, summary: s, items } = detail;19  const rows = [...s.items].sort((a, b) => (b.valueUsd ?? 0) - (a.valueUsd ?? 0));20  return (21    <div className="mx-auto max-w-4xl print:max-w-none">22      <div className="mb-4 flex items-center justify-between print:hidden">23        <p className="text-sm text-muted">Use your browser&apos;s print dialog to save as PDF.</p>24        <PrintButton />25      </div>26      <article className="card p-8 print:border-0 print:p-0 print:shadow-none">27        <header className="flex items-start justify-between border-b border-border pb-4">28          <div>29            <p className="text-[11px] font-semibold uppercase tracking-wider text-subtle">RareIndex · Collection schedule</p>30            <h1 className="mt-1 text-2xl font-semibold tracking-tight">{c.name}</h1>31            <p className="text-sm text-muted">32              Prepared for {u.name ?? u.email} · {fmtDate(new Date())} · values in {d.currency}33            </p>34          </div>35          <div className="text-right">36            <p className="text-[11px] uppercase tracking-wider text-subtle">Total estimated value</p>37            <p className="num text-2xl font-semibold">{s.valuedCount ? d.money(s.valueUsd) : '—'}</p>38            <p className="text-xs text-muted">39              {s.valuedCount}/{s.itemCount} items valued · confidence {confidenceLabel(s.confidence)}40            </p>41          </div>42        </header>43        <table className="mt-4 w-full text-[12px]">44          <thead>45            <tr className="border-b border-border text-left text-[10px] uppercase tracking-wider text-subtle">46              <th className="py-1.5 pr-2">#</th>47              <th className="py-1.5 pr-2">Item</th>48              <th className="py-1.5 pr-2">Grade / condition</th>49              <th className="py-1.5 pr-2">Cert / serial</th>50              <th className="py-1.5 pr-2 text-right">Qty</th>51              <th className="py-1.5 pr-2 text-right">Acquired</th>52              <th className="py-1.5 pr-2 text-right">Cost</th>53              <th className="py-1.5 pr-2 text-right">Est. value</th>54              <th className="py-1.5 text-right">Basis</th>55            </tr>56          </thead>57          <tbody>58            {rows.map((i, idx) => {59              const src = items.find((x) => x.id === i.id)!;60              return (61                <tr key={i.id} className="border-b border-border align-top">62                  <td className="py-1.5 pr-2 text-subtle">{idx + 1}</td>63                  <td className="py-1.5 pr-2">64                    <p className="font-medium">{i.title}</p>65                    <p className="text-[11px] text-muted">{i.categorySlug.replace(/_/g, ' ')}{src.source ? ` · from ${src.source}` : ''}</p>66                  </td>67                  <td className="py-1.5 pr-2">{src.variantLabel ?? (i.grader ? `${i.grader.toUpperCase()} ${i.grade ?? ''}` : src.condition ?? '—')}</td>68                  <td className="py-1.5 pr-2 font-mono text-[11px]">{[src.certificationNumber, src.serial].filter(Boolean).join(' / ') || '—'}</td>69                  <td className="num py-1.5 pr-2 text-right">{i.quantity}</td>70                  <td className="num py-1.5 pr-2 text-right">{i.acquiredAt ?? '—'}</td>71                  <td className="num py-1.5 pr-2 text-right">{i.costUsd === null ? '—' : d.money(i.costUsd)}</td>72                  <td className="num py-1.5 pr-2 text-right font-medium">{i.valueUsd === null ? 'unavailable' : d.money(i.valueUsd)}</td>73                  <td className="py-1.5 text-right text-[11px] text-muted">{i.valueSource === 'manual' ? 'owner estimate' : i.valueSource === 'none' ? '—' : `RIV · ${confidenceLabel(i.confidence).toLowerCase()}`}</td>74                </tr>75              );76            })}77          </tbody>78        </table>79        <footer className="mt-6 text-[11px] leading-relaxed text-subtle">80          <p>81            RIV (RareIndex Valuation) figures are statistical estimates derived from observed public sales and listings, each with a confidence level and sample size; they are not appraisals, authentication or a guarantee of insurable value. Items marked &quot;owner estimate&quot; use a value entered by the collection owner. Items marked &quot;unavailable&quot; have insufficient market evidence. Historical purchase costs are converted to {d.currency} at the exchange rate of the acquisition date. RareIndex does not authenticate items.82          </p>83        </footer>84      </article>85    </div>86  );87}88